package com.study.crypto.certificate.server.service;

import com.study.crypto.certificate.server.config.RedisComponent;
import com.study.crypto.certificate.server.entity.CertificateApply;
import com.study.crypto.certificate.server.entity.Certification;
import com.study.crypto.certificate.server.mapper.CertificateApplyMapper;
import com.study.crypto.certificate.server.mapper.CertificationMapper;
import com.study.crypto.dto.RequestRandomDto;
import com.study.crypto.dto.ResponseRandomDto;
import com.study.crypto.dto.ca.*;
import com.study.crypto.dto.ca.ResponseCheckResultSealCertDto.CheckResultSealCertDtoData;
import com.study.crypto.dto.ca.ResponseCheckResultSealCertDto.CheckResultSealCertDtoPackage;
import com.study.crypto.general.spring.interceptor.TraceIdInterceptor;
import com.study.crypto.signer.Signer;
import com.study.crypto.signer.exception.MyRuntimeException;
import com.study.crypto.utils.CertUtils;
import com.study.crypto.utils.EssPdfUtil;
import com.study.crypto.utils.KeyUtils;
import com.study.crypto.utils.ResourceUtils;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCSException;
import org.bouncycastle.util.encoders.Base64;
import org.slf4j.MDC;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Songjin
 * @since 2022-11-01 14:55
 */
@Transactional(rollbackFor = Exception.class)
@Service
public class CertificateIssuerService implements InitializingBean {

    /** 服务器证书 */
    private Certificate serverCert;
    private final Map<String, ContentSigner> issuerSignerMap = new ConcurrentHashMap<>();
    private final Map<String, Certificate> issuerCertMap = new ConcurrentHashMap<>();

    @Autowired
    private RedisComponent redis;
    @Autowired
    private CertificationMapper certificationMapper;
    @Autowired
    private CertificateApplyMapper certificateApplyMapper;

    /**
     * 获取随机数
     * @param request 请求数据
     * @return ResponseRandomDto
     */
    public ResponseRandomDto applyServiceRandom(RequestRandomDto request) {
        ResponseRandomDto response = new ResponseRandomDto();
        response.setResultCode(CaConstants.SUCCESS);
        response.setResultCodeMsg("随机数申请成功");
        response.setRandomB(EssPdfUtil.genRandomUuid().substring(0, 16));
        this.redis.set(request.getRandomA().concat(response.getRandomB()), "applyServiceRandom", 60L);
        return response;
    }

    /**
     * 证书申请请求，单证，只有签名证书
     * @param request 请求数据
     * @return ResponseSealCertDto
     * @throws Exception 异常
     */
    public ResponseCheckResultSealCertDto applySingleCert(RequestSealCertDistinctNameDto request) throws Exception {
        List<CertificateApply> applies = CertificateApply.getInstances(request);
        CheckResultSealCertDtoData certDtoData = doIssueSingleCertInternal(applies.get(0));
        ResponseCheckResultSealCertDto response = new ResponseCheckResultChangeCertDto();
        response.setResultCode(CaConstants.SUCCESS);
        response.setResultCodeMsg("证书申请成功");
        response.getData().add(certDtoData);
        return response;
    }

    private CheckResultSealCertDtoData doIssueSingleCertInternal(CertificateApply apply) throws Exception {
        apply.setTaskId(MDC.get(TraceIdInterceptor.TRACE_ID));
        X500Name subject = CertUtils.buildDistinctName(apply.getDn());
        LocalDateTime notBefore = LocalDateTime.now();
        LocalDateTime notAfter = notBefore.plusYears(1);
        boolean sm2Or = GMObjectIdentifiers.sm2p256v1.getId().equals(apply.getAlgorithm());
        String certType = sm2Or ? Certification.CERT_TYPE_SM2 : Certification.CERT_TYPE_RSA;
        ContentSigner issueSigner = issuerSignerMap.get(certType);
        Certificate issuerCert = issuerCertMap.get(certType);
        String serial_ = certificationMapper.selectMaxSerialNumber(certType);
        BigInteger serial = new BigInteger(serial_, 16).add(BigInteger.valueOf(1));
        SubjectPublicKeyInfo publicKeyInfo;
        if (sm2Or) {
            // SM2 公钥需要转换成 SubjectPublicKeyInfo 对象
            publicKeyInfo = KeyUtils.obtainSubjectPublicKeyInfoBase64(apply.getSubjectPublicKeyInfo());
        } else {
            publicKeyInfo = SubjectPublicKeyInfo.getInstance(Base64.decode(apply.getSubjectPublicKeyInfo()));
        }
        Certificate signCert = CertUtils.generateUserCertificate(publicKeyInfo, subject, issueSigner, issuerCert, notBefore, notAfter, serial);
        int signcertUsage = CertUtils.certificateUsage(signCert.getEncoded(ASN1Encoding.DER));
        Certification signCert_ = Certification.builder()
                                               .beginTime(notBefore)
                                               .endTime(notAfter)
                                               .certDn(subject.toString())
                                               .keyId(apply.getKeystoreId())
                                               .certType(certType)
                                               .serialNumber(serial.toString(16).toUpperCase())
                                               .keyUsage(signcertUsage)
                                               .certificate(signCert.getEncoded(ASN1Encoding.DER))
                                               .build();
        // 将客户端证书申请录入数据库
        certificateApplyMapper.insert(apply);
        // 证书数据录入数据库
        signCert_.setApplyId(apply.getId());
        certificationMapper.insert(signCert_);

        CheckResultSealCertDtoPackage package_ = new CheckResultSealCertDtoPackage();
        package_.setSignCert(Base64.toBase64String(signCert.getEncoded(ASN1Encoding.DER)));
        CheckResultSealCertDtoData certDtoData = new CheckResultSealCertDtoData();
        certDtoData.setRequestID(apply.getRequestId());
        certDtoData.setErrorCode(CaConstants.SUCCESS);
        certDtoData.setPackage_(package_);
        return certDtoData;
    }

    /**
     * 证书申请请求
     * @param request 请求数据
     * @return ResponseSealCertDto
     * @throws Exception 异常
     */
    public ResponsePkcs10Dto applySingleCertByPkcs10(RequestPkcs10Dto request) throws Exception {
        List<CertificateApply> applies = CertificateApply.getInstances(request);
        CheckResultSealCertDtoData certDtoData = doIssueSingleCertInternal(applies.get(0));
        ResponsePkcs10Dto response = new ResponsePkcs10Dto();
        response.setResultCode(CaConstants.SUCCESS);
        response.setResultCodeMsg("证书申请成功");
        response.getData().add(certDtoData);
        return response;
    }

    /**
     * 证书申请请求
     * @param request 请求数据
     * @return ResponseSealCertDto
     */
    public ResponseSealCertDto applySealCert(RequestSealCertDto request) {
        ResponseSealCertDto response = new ResponseSealCertDto();
        String tokenInfo = request.getTokenInfo();
        String redisVal = (String) this.redis.get(tokenInfo);
        if (StringUtils.isBlank(redisVal)) {
            response.setResultCode(CaConstants.APPLY_SEAL_CERT_FAILED);
            response.setResultCodeMsg("token过期或不存在");
            return response;
        }
        // tokeninfo 使用之后删除
        this.redis.del(tokenInfo);
        // 将客户端证书申请录入数据库
        List<CertificateApply> applies = CertificateApply.getInstances(request);
        String taskId = MDC.get(TraceIdInterceptor.TRACE_ID);
        for (CertificateApply apply : applies) {
            apply.setTaskId(taskId);
            certificateApplyMapper.insert(apply);
        }
        // 返回客户端数据
        response.setResultCode(CaConstants.SUCCESS);
        response.setResultCodeMsg("证书申请请求处理成功");
        response.getData().setTaskId(taskId);
        return response;
    }

    /**
     * 使用taskId查询下载证书
     * @param request 请求数据
     * @return ResponseSealCertDto
     * @throws Exception 异常
     */
    public ResponseCheckResultSealCertDto checkResult(RequestCheckResultDto request) throws Exception {
        String taskId = request.getData().getTaskId();
        CertificateApply apply_ = CertificateApply.builder().taskId(taskId).build();
        CertificateApply apply = certificateApplyMapper.selectOne(apply_);

        // 产生双证书
        CheckResultSealCertDtoData certDtoData = doIssueDoubleCertInternal(apply);
        ResponseCheckResultSealCertDto response = new ResponseCheckResultChangeCertDto();
        response.setResultCode(CaConstants.SUCCESS);
        response.setResultCodeMsg("证书申请成功");
        response.getData().add(certDtoData);
        return response;
    }

    private CheckResultSealCertDtoData doIssueDoubleCertInternal(CertificateApply apply) throws Exception {
        Certification certification_ = Certification.builder().applyId(apply.getId()).build();
        List<Certification> certifications = certificationMapper.select(certification_);
        byte[] envelopedKey;
        Certificate signCert;
        Certificate encCert;
        if (CollectionUtils.isEmpty(certifications)) {
            X500Name subject = CertUtils.buildDistinctName(apply.getCountryName(), apply.getOrganizationName(), apply.getCommonName());
            LocalDateTime notBefore = LocalDateTime.now();
            LocalDateTime notAfter = notBefore.plusYears(1);

            String serialNumber = certificationMapper.selectMaxSerialNumber(Certification.CERT_TYPE_SM2);
            BigInteger serial = new BigInteger(serialNumber, 16).add(BigInteger.valueOf(1));
            BigInteger encCertSerial = serial.add(BigInteger.valueOf(1));
            String publicKeyBase64 = apply.getSubjectPublicKeyInfo();
            KeyPair keyPair = KeyUtils.generateKeyPair(KeyUtils.ALGO_SM2);
            ContentSigner signer = issuerSignerMap.get(Certification.CERT_TYPE_SM2);
            signCert = CertUtils.generateUserCertificate(publicKeyBase64, subject, signer, serverCert, notBefore, notAfter, serial);
            encCert = CertUtils.generateUserEncCertificate(keyPair, subject, signer, serverCert, notBefore, notAfter, encCertSerial);
            int signcertUsage = CertUtils.certificateUsage(signCert.getEncoded(ASN1Encoding.DER));
            int enccertUsage = CertUtils.certificateUsage(encCert.getEncoded(ASN1Encoding.DER));
            envelopedKey = KeyUtils.generateEnvelopedKey(publicKeyBase64, keyPair);
            Certification signCert_ = Certification.builder()
                                                   .beginTime(notBefore)
                                                   .endTime(notAfter)
                                                   .certDn(subject.toString())
                                                   .keyId(apply.getKeystoreId())
                                                   .certType(Certification.CERT_TYPE_SM2)
                                                   .serialNumber(serial.toString(16).toUpperCase())
                                                   .keyUsage(signcertUsage)
                                                   .certificate(signCert.getEncoded(ASN1Encoding.DER))
                                                   .applyId(apply.getId())
                                                   .build();
            Certification encCert_ = new Certification();
            BeanUtils.copyProperties(signCert_, encCert_);
            encCert_.setKeyUsage(enccertUsage);
            encCert_.setCertificate(encCert.getEncoded(ASN1Encoding.DER));
            encCert_.setSerialNumber(encCertSerial.toString(16).toUpperCase());
            encCert_.setEnvelopedKey(envelopedKey);
            certificationMapper.insert(signCert_);
            certificationMapper.insert(encCert_);
        } else {
            Certification cert0 = certifications.get(0);
            Certification cert1 = certifications.get(1);
            boolean digitalSignature = CertUtils.usageDigitalSignature(cert0.getCertificate());
            if (digitalSignature) {
                envelopedKey = cert1.getEnvelopedKey();
                signCert = CertUtils.convertCertificate(cert0.getCertificate());
                encCert = CertUtils.convertCertificate(cert1.getCertificate());
            } else {
                envelopedKey = cert0.getEnvelopedKey();
                signCert = CertUtils.convertCertificate(cert1.getCertificate());
                encCert = CertUtils.convertCertificate(cert0.getCertificate());
            }
        }
        CheckResultSealCertDtoPackage package_ = new CheckResultSealCertDtoPackage();
        package_.setSignCert(Base64.toBase64String(signCert.getEncoded(ASN1Encoding.DER)));
        package_.setEncCert(Base64.toBase64String(encCert.getEncoded(ASN1Encoding.DER)));
        package_.setEncKeyProtection(Base64.toBase64String(envelopedKey));
        CheckResultSealCertDtoData certDtoData = new CheckResultSealCertDtoData();
        certDtoData.setRequestID(apply.getRequestId());
        certDtoData.setErrorCode(CaConstants.SUCCESS);
        certDtoData.setPackage_(package_);
        return certDtoData;
    }

    /**
     * 证书申请请求，使用 pkcs10 申请
     * @param request 请求数据
     * @return ResponsePkcs10Dto
     * @throws Exception 异常
     */
    public ResponsePkcs10Dto applyCertByPkcs10(RequestPkcs10Dto request) throws Exception {
        ResponsePkcs10Dto response = new ResponsePkcs10Dto();
        String redisVal = (String) this.redis.get(request.getTokenInfo());
        if (StringUtils.isBlank(redisVal)) {
            response.setResultCode(CaConstants.APPLY_SEAL_CERT_FAILED);
            response.setResultCodeMsg("token过期或不存在");
            return response;
        }
        // tokeninfo 使用之后删除
        this.redis.del(request.getTokenInfo());

        // 将客户端证书申请录入数据库，产生证书数据
        List<CertificateApply> applies = CertificateApply.getInstances(request);
        String taskId = MDC.get(TraceIdInterceptor.TRACE_ID);
        for (CertificateApply apply : applies) {
            apply.setTaskId(taskId);
            certificateApplyMapper.insert(apply);

            CheckResultSealCertDtoData certDtoData = doIssueDoubleCertInternal(apply);
            response.getData().add(certDtoData);
        }
        response.setResultCode(CaConstants.SUCCESS);
        response.setResultCodeMsg("证书申请成功");
        return response;
    }

    @Override
    public void afterPropertiesSet() {
        try {
            // 服务端证书
            byte[] serverCertBytes = ResourceUtils.toByteArray("certs/sm2-server-cert.cer");
            byte[] serverCertBytesRSA = ResourceUtils.toByteArray("certs/rsa-server-cert.cer");
            this.serverCert = Certificate.getInstance(serverCertBytes);
            Certificate serverCertRSA = Certificate.getInstance(serverCertBytesRSA);
            issuerCertMap.put(Certification.CERT_TYPE_SM2, serverCert);
            issuerCertMap.put(Certification.CERT_TYPE_RSA, serverCertRSA);
            // 服务端私钥
            String pemStringSM2 = ResourceUtils.readFileToString("certs/sm2-server-key.keystore");
            String pemStringECC = ResourceUtils.readFileToString("certs/ecc-server-key.keystore");
            String pemStringRSA = ResourceUtils.readFileToString("certs/rsa-server-key.keystore");
            PrivateKey serverPrivateKeySM2 = KeyUtils.privateKey(pemStringSM2);
            PrivateKey serverPrivateKeyECC = KeyUtils.privateKey(pemStringECC);
            PrivateKey serverPrivateKeyRSA = KeyUtils.privateKey(pemStringRSA);
            ContentSigner issuerSignerSM2 = new JcaContentSignerBuilder(Signer.SIGN_ALGO_SM3_SM2).build(serverPrivateKeySM2);
            ContentSigner issuerSignerECC = new JcaContentSignerBuilder(Signer.SIGN_ALGO_SHA256_ECDSA).build(serverPrivateKeyECC);
            ContentSigner issuerSignerRSA = new JcaContentSignerBuilder(Signer.SIGN_ALGO_SHA256_RSA).build(serverPrivateKeyRSA);
            issuerSignerMap.put(Certification.CERT_TYPE_SM2, issuerSignerSM2);
            issuerSignerMap.put(Certification.CERT_TYPE_ECC, issuerSignerECC);
            issuerSignerMap.put(Certification.CERT_TYPE_RSA, issuerSignerRSA);
        } catch (IOException | PKCSException | OperatorException e) {
            throw new MyRuntimeException(e);
        }
    }

}
